﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace My2048
{
    class Program
    {
        //2048核心算法
        /*上下左右移动
         * 上移
         *  获取列数据
         *  2 2 0 2 --》4 2 0 0
         *      合并数据
         *          相邻相同，则合并
         *      移动
         *          把0移动到末尾
         * 将列数据还原到二维数组
         * 
         * 下移
         *    从下到上获取数据
         *    使用上移方法
         * 左移
         *     从左到右获取数据
         *     使用上移方法
         * 右移
         *     从右到左获取数据
         *     使用上移方法
         */
        static void Main()
        {
            Game2018();
        }

        //在控制台中显示游戏数据
        private static void PrintGame(int[,] array)
        {
            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine();//设置输出到控制台中时上面有三行空行

            //在控制台中显示数据
            for (int row = 0; row < array.GetLength(0); row++)
            {
                for (int column = 0; column < array.GetLength(1); column++)
                {
                    if (array[row, column] == 0)
                    {
                        Console.Write("\t_");
                    }
                    else
                    {
                        Console.Write("\t" + array[row, column]);
                    }
                }
                Console.WriteLine();
                Console.WriteLine();
                Console.WriteLine();
            }
            Console.WriteLine();
            Console.WriteLine();
        }

        //生成初始游戏数据
        private static int[,] GetInitData()
        {
            int[,] gameData = new int[4, 4];//初始化一个空二维数组，储存游戏数据

            //随机挑选两个位置，随机放置2和4
            Random rand = new Random();//初始化一个随机数生成器
            for (int i = 0; i < 2; i++)
            {
                int row = rand.Next(0, 4);//随机行数
                int column = rand.Next(0, 4);//随机列数
                int data = rand.Next(1, 3) * 2;//随机数据
                gameData[row, column] = data;//赋值
            }

            //这里本应该使用随机生成的数据，但是先设置一个默认数据，用于开发测试各种情况
            //gameData = new int[4, 4]
            //{
            //    { 2,2,0,0},
            //    { 0,4,2,0},
            //    { 2,2,2,8},
            //    { 0,0,2,2},
            //};
            return gameData;
        }

        /// <summary>
        /// 用于旋转二维数组，行变列，列变行
        /// </summary>
        /// <param name="sourceArray"></param>
        /// <param name="angle">旋转角度（90度的整数倍,正数为逆时针）</param>
        /// <returns>变换后的数组</returns>
        private static int[,] RotateArray(int[,] sourceArray,int angle)
        {
            //先将角度变换为正数
            if (angle<0)
            {
                angle = 360 + angle;
            }
            if (angle>360)
            {
                angle = angle % 360;
            }
            int[,] tempArray = new int[sourceArray.GetLength(1), sourceArray.GetLength(0)];
            switch (angle)
            {
                case 90:
                    //原始数组：列数反序，行数正序
                    for (int column = sourceArray.GetLength(1) - 1; column >= 0; column--)
                    {
                        //获取原始数组的列，装到临时数组的行里
                        int[] columnData = new int[sourceArray.GetLength(0)];
                        for (int row = 0; row < sourceArray.GetLength(0); row++)
                        {
                            tempArray[sourceArray.GetLength(1) - 1 - column, row]= sourceArray[row, column];
                        }
                    }
                    break;
                case 180:
                    //转两次90，这个就是 行不变，列逆序
                    sourceArray = RotateArray(sourceArray, 90);
                    tempArray = RotateArray(sourceArray, 90);
                    break;
                case 270:
                    //转三次90
                    sourceArray = RotateArray(sourceArray, 90);
                    sourceArray = RotateArray(sourceArray, 90);
                    tempArray = RotateArray(sourceArray, 90);
                    break;
            }
            return tempArray;
        }

        //对获取的一维数组进行数据处理
        private static int[] DealData(int[] array)
        {
            /*处理数据的方法为：
             * 1、创一个新的数组用来存放处理后的数据
             * 2、逐个访问原来的数组，并判断当前数据是否为0
             *      如果不为0，则放到处理后的数组中
             * 3、判断当前数据是否和后一个数据相同，
             *      如果相同则合并，同时不再访问后一个数据
             */
            int[] tempArray = new int[array.Length];
            int index = 0;//tempArray将要使用第几个数据

            for (int i = 0; i < array.Length; i++)
            {
                if (array[i] != 0)
                {
                    //当目前判断数据不为0时，就将他放到结果数组中
                    if (tempArray[index]==0)
                    {
                        //结果数组使用到一个空位置
                        tempArray[index] += array[i];
                    }
                    else if (tempArray[index] == array[i])
                    {
                        //当结果数组当前位置不为0，且和目前判断数据相等时，赋值后index + 1
                        tempArray[index++] += array[i];
                    }
                    else
                    {
                        //结果数组当前位置不为0，且和判断数据不相同时
                        tempArray[++index] += array[i];//先加先自增，即：将判断数据放入下一个位置
                    }
                }
            }
            return tempArray;
        }

        //上移操作的主函数，不需要返回值，直接修改gameData
        private static void MoveUp(int[,] gameData)
        {
            //上移是对列的操作，从上到下获取数据

            //循环获取每一列，并进行相关操作
            for (int column = 0; column < gameData.GetLength(1); column++)
            {
                //从上到下，获取某一列
                int[] columnData = new int[gameData.GetLength(0)];
                for (int i = 0; i < gameData.GetLength(0); i++)
                {
                    columnData[i] = gameData[i, column];
                }
                columnData = DealData(columnData);//对这一列数据进行处理，处理后的数据还在columnData里

                //将处理后的数据重新放入gameData
                for (int i = 0; i < gameData.GetLength(0); i++)
                {
                    gameData[i, column] = columnData[i];
                }
            }
        }

        //将数组逆时针旋转180度就可以使用MoveUp
        private static void MoveDown(int[,] gameData)
        {
            //旋转180度
            int[,] tempArray = RotateArray(gameData, 180);
            MoveUp(tempArray);
            tempArray = RotateArray(tempArray, -180);
            for (int row = 0; row < tempArray.GetLength(0); row++)
            {
                for (int column = 0; column < tempArray.GetLength(1); column++)
                {
                    gameData[row, column] = tempArray[row, column];
                }
            }
        }

        //将数组逆时针旋转90度就可以使用MoveUp
        private static void MoveRight(int[,] gameData)
        {
            //旋转90度
            int[,] tempArray = RotateArray(gameData, 90);
            MoveUp(tempArray);
            tempArray = RotateArray(tempArray, -90);//再旋转回来
            for (int row = 0; row < tempArray.GetLength(0); row++)
            {
                for (int column = 0; column < tempArray.GetLength(1); column++)
                {
                    gameData[row, column] = tempArray[row, column];
                }
            }
        }

        //将数组逆时针旋转270度就可以使用MoveUp
        private static void MoveLeft(int[,] gameData)
        {
            //旋转270度
            int[,] tempArray = RotateArray(gameData, 270);
            MoveUp(tempArray);
            tempArray = RotateArray(tempArray, -270);//再旋转回来
            for (int row = 0; row < tempArray.GetLength(0); row++)
            {
                for (int column = 0; column < tempArray.GetLength(1); column++)
                {
                    gameData[row, column] = tempArray[row, column];
                }
            }
        }

        //判断结束条件
        private static bool IfEnd(int[,] gameData)
        {
            /*游戏结束条件：
             *只有所有空格均填满，且无法再次操作时结束游戏
             *因为主程序中是先添加空位，再进行的判断，所以有空位，就一定可以继续操作
             */
            foreach (var item in gameData)
            {
                if (item==0)
                {
                    //任意一个位置为空，则可以继续游戏
                    return true;
                }
            }
            //2048是一个4*4的数据结构，只要对左上角的3*3进行是否和周围相同的判断即可
            //最后在对剩余的 [3,3] 位置单独判断
            for (int row = 0; row < gameData.GetLength(0)-1; row++)
            {
                for (int column = 0; column < gameData.GetLength(1)-1; column++)
                {
                    if (gameData[row,column] != gameData[row+1, column] && gameData[row, column] != gameData[row, column+1])
                    {
                        //当目前位置的数据不等于下边的，或不等于右边的则结束游戏
                        return false;
                    }
                }
            }
            if (gameData[gameData.GetLength(0)-1, gameData.GetLength(1)-1] != gameData[gameData.GetLength(0) - 2, gameData.GetLength(1) - 1] 
                && gameData[gameData.GetLength(0) - 1, gameData.GetLength(1) - 1] != gameData[gameData.GetLength(0) - 1, gameData.GetLength(1) - 2])
            {
                //最后一个位置既不等于他的上面的，又不等于他的左边的，则游戏结束
                return false;
            }
            return true;
        }

        //判断是否达成2048
        private static bool If2048(int[,] gameData)
        {
            foreach (var item in gameData)
            {
                if (item==2048)
                {
                    return true;
                }
            }
            return false;
        }

        //随机添加数字
        private static void AddData(int[,] gameData)
        {
            //随机挑选一个位置，随机添加 2 或 4

            Random rand = new Random();
            string emptyLocation = "";
            int emptyCount = 0;

            for (int row = 0; row < gameData.GetLength(0) - 1; row++)
            {
                for (int column = 0; column < gameData.GetLength(1) - 1; column++)
                {
                    if (gameData[row, column] == 0)
                    {
                        //记录空位置空位置
                        emptyLocation += row.ToString() + column.ToString();
                        emptyCount++;
                    }
                }
            }
            int addLocation = rand.Next(0, emptyCount);
            int rowLocation = int.Parse(emptyLocation[addLocation*2].ToString());
            int columnLocation = int.Parse(emptyLocation[addLocation * 2+1].ToString());
            if (rand.Next(1, 11) == 10)
            {
                //只有十分之一的几率添加4
                gameData[rowLocation, columnLocation] = 4;
                return;
            }
            else
            {
                gameData[rowLocation, columnLocation] = 2;
                return;
            }
        }

        //2048主函数
        private static void Game2018()
        {
            //设置一个二维数组储存游戏数据
            int[,] gameData = GetInitData();//获取初始游戏数据
            PrintGame(gameData);

            while (true)
            {
                //游戏第一次进入，或还可以继续操作
                Console.Write("请输入操作(w-上，s-下，a-左，d-右）：");//使用Write可以直接再后面输入内容，并读取
                string direction = Console.ReadKey(true).Key.ToString();
                direction = direction.ToLower();

                //在此处判断用户输入，用户可以输入数据说明：上一轮循环的最后if判定游戏还未结束
                switch (direction)
                {
                    case "w":
                        MoveUp(gameData);//上移操作
                        AddData(gameData);//先在空位上添加数字，在进行判断
                        Console.Clear();
                        PrintGame(gameData);//每执行完一次交互，就显示一次交互结果
                        break;
                    case "a":
                        MoveLeft(gameData);//左移
                        AddData(gameData);
                        Console.Clear();
                        PrintGame(gameData);
                        break;
                    case "s":
                        MoveDown(gameData);//下移
                        AddData(gameData);
                        Console.Clear();
                        PrintGame(gameData);
                        break;
                    case "d":
                        MoveRight(gameData);//右移
                        AddData(gameData);
                        Console.Clear();
                        PrintGame(gameData);
                        break;
                    default:
                        Console.WriteLine("输入内容有误，请重新输入");
                        break;
                }

                //这里是先添加数字，再判断报喜
                if (If2048(gameData))
                {
                    Console.WriteLine("恭喜达成2048！！！！游戏将继续....");
                }

                if (!IfEnd(gameData))
                {
                    //当判断可以结束游戏时，终止while循环，
                    //否则说明添加完数字的情况下还可以操作，则进行下一次循环
                    Console.WriteLine("无法继续操作，游戏结束！！！！");
                    break;
                }
            }
        }
    }
}
